import { App } from "@/models/app";
import { Component, ComponentTag } from "@/models/component";
import { Container } from "@/models/containers";
import { createUpdateId, EChart } from "@/models/reactiveComponent";
import { TCpId } from "@/models/types";

export type TComponentServices = ReturnType<typeof getServices>;

export function getServices(app: App) {
  const mId2cpMap = new Map<string, Component>();
  const mUpdateId2cpMap = new Map<string, TCpId>();

  for (const cp of iterComponent(app)) {
    mId2cpMap.set(cp.id, cp);

    if (cp.tag === ComponentTag.EChart) {
      const chartCp = cp as EChart;

      chartCp.chartInfos.forEach((info, idx) => {
        const updateId = createUpdateId(chartCp, idx);
        mUpdateId2cpMap.set(updateId, chartCp.id);
      });
    } else if (cp.tag === ComponentTag.Foreach) {
      mUpdateId2cpMap.set(cp.id, cp.id);
    } else if ("updateInfos" in cp) {
      mUpdateId2cpMap.set(cp.id, cp.id);
    }

    if ("visible" in cp && typeof cp["visible"] !== "boolean") {
      mUpdateId2cpMap.set(cp.id, cp.id);
    }
  }

  function getComponent(id: string) {
    const cp = mId2cpMap.get(id);
    if (!cp) {
      throw new Error(`not found Component[id:${id}]`);
    }

    return cp;
  }

  function getComponentByUpdateId(updateId: string) {
    const cpId = mUpdateId2cpMap.get(updateId);
    if (!cpId) {
      throw new Error(`not found updateId[${updateId}]`);
    }

    return getComponent(cpId);
  }

  function getApp() {
    return app;
  }

  return {
    getComponent,
    getComponentByUpdateId,
    getApp,
  };
}

export function* iterComponent(app: App) {
  const stack = [app] as Container[];
  if (app.drawer) {
    stack.push(app.drawer);
  }

  while (stack.length > 0) {
    const target = stack.pop()!;

    for (const c of target.children) {
      if ((c as Container).children) {
        stack.push(c as Container);
      }
      yield c;
    }
  }
}
